
from numba import jit
import math
import numpy as np
from sklearn.metrics import make_scorer

@jit
def mae_loss(y_true, y_pred):
    x = y_pred - y_true
    grad = np.sign(x)
    hess = np.zeros_like(x)
    return grad, hess

@jit
def pseudo_huber_loss(y_true, y_pred):
    x = y_pred - y_true
    scale = 1 + (x / delta) ** 2
    scale_sqrt = np.sqrt(scale)
    grad = x / scale_sqrt
    hess = 1 / (scale * scale_sqrt)
    return grad, hess

@jit
def mae_huber_loss(y_pred, dtrain, huber_delta = 60000):
    '''
    xgb目标函数，mae一阶导数为常数二阶导数为零，考虑尝试 mae+huber
    :param y_pred: 累计到上一轮的预测结果
    :param dtrain: xgb.Dmatrix y_true
    :param huber_delta:
    :return:
    '''
    y_true = dtrain.get_label()
    y_len = len(y_true)
    grad = np.zeros(y_len)
    hess = np.zeros(y_len)
    for i in range(y_len):
        grad_mae, hess_mae = mae_loss(y_true[i], y_pred[i])
        grad_huber, hess_huber = pseudo_huber_loss(y_true[i], y_pred[i], huber_delta)
        grad[i] = 0.5 * grad_mae + 0.5 * grad_huber
        hess[i] = 0.5 * hess_mae + 0.5 * hess_huber
    return grad, hess


@jit
def pseudo_huber_loss_obj(y_pred, dtrain, delta = 60000):
    '''
    xgb目标函数，仅仅使用huber_loss
    :param y_pred:
    :param dtrain:
    :param delta:
    :return:
    '''
    y_true = dtrain.get_label()
    y_len = len(y_true)
    grad = np.zeros(y_len)
    hess = np.zeros(y_len)
    for i in range(y_len):
        x = y_pred[i] - y_true[i]
        scale = 1 + (x / delta) ** 2
        scale_sqrt = np.sqrt(scale)
        grad[i] = x / scale_sqrt
        hess[i] = 1 / (scale * scale_sqrt)
    return grad, hess


def more_uptostand_func(y_true, y_pred):
    '''
    基于业务目标所定义的寻优目标，寻找更多可以让dealer的误差率达标的模型。该评价函数返回的值越大越好

    根据API规则编写
    https://scikit-learn.org/stable/modules/model_evaluation.html#scoring

    :return scoring_val: gridsearchCV的评价函数，值越大越好
    '''
    y_true = np.array(y_true)
    y_pred = np.array(y_pred)
    scoring_array = np.abs(y_true - y_pred) / y_true
    scoring_val = (scoring_array <= 0.15).sum()
    
    return scoring_val


more_uptostand_score = make_scorer(more_uptostand_func, greater_is_better=True)
